package cn.itsource.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.itsource.api.RuleStartRemoteApi;
import cn.itsource.constants.*;
import cn.itsource.domain.dto.DriverSettingRedisDto;
import cn.itsource.domain.dto.RuleDto;
import cn.itsource.exception.GlobalException;
import cn.itsource.mapper.OrderBillMapper;
import cn.itsource.mapper.OrderMapper;
import cn.itsource.pojo.domain.Login;
import cn.itsource.pojo.domain.Order;
import cn.itsource.pojo.domain.OrderBill;
import cn.itsource.pojo.dto.ConfirmFeesDto;
import cn.itsource.pojo.dto.GeoDriverDto;
import cn.itsource.pojo.dto.OrderDto;
import cn.itsource.pojo.dto.PushDriverOrderMsgDto;
import cn.itsource.pojo.vo.OrderInfoVo;
import cn.itsource.pojo.vo.Position;
import cn.itsource.pojo.vo.RunningOrderInfoVo;
import cn.itsource.result.JSONResult;
import cn.itsource.service.IOrderService;
import cn.itsource.utils.CodeGenerateUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.messaging.support.MessageBuilder.withPayload;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author zong
 * @since 2024-09-19
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    @Autowired
    private RuleStartRemoteApi ruleStartRemoteApi;
    @Autowired
    private OrderBillMapper orderBillMapper;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private DefaultMQPullConsumer pullConsumer;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 司机确认费用
     * @param confirmFeesDto
     * @return
     */
    @Transactional
    @Override
    public JSONResult confirmFees(ConfirmFeesDto confirmFeesDto) {
        //算钱
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, confirmFeesDto.getOrderNo())
                .eq(Order::getStatus, Constants.orderStatus.ORDER_STATUS_COMPLETE_DRIVED));
        if(one == null){
           return JSONResult.error("未查询到订单数据");
        }
        //结束订单的修改
        updateOrder(one);
        //修改账单数据
        OrderBill orderBill = updateOrderBill(confirmFeesDto, one);
        return JSONResult.success();
    }

    private OrderBill updateOrderBill(ConfirmFeesDto confirmFeesDto, Order one) {
        //根据订单id查询账单
        OrderBill orderBill = orderBillMapper.selectOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderId, one.getId()));
        orderBill.setRealOrderAmount(one.getRealOrderAmount());
        BigDecimal bigDecimal = new BigDecimal(confirmFeesDto.getTollFee());
        orderBill.setTollAmount(bigDecimal);
        orderBill.setFavourAmount(one.getFavourAmount());
        //exceed_base_mileage = 实际里程 - 基础里程
        if(one.getRealMileage().compareTo(orderBill.getBaseMileage()) > RConst.Num.INT_0){
            orderBill.setExceedBaseMileage(one.getRealMileage().subtract(orderBill.getBaseMileage()));
            //exceed_base_mileage_amount = 超出公里数 * 超出公里价格
            orderBill.setExceedBaseMileageAmount(orderBill.getExceedBaseMileage()
                    .multiply(orderBill.getExceedBaseMileageEveryKmAmount()));
        }
        //里程费(基础+超出)
        orderBill.setMileageAmount(orderBill.getBaseMileageAmount().add(orderBill.getExceedBaseMileageAmount()));
        //等待时长 = 开始代驾 时间 - 到达时间
        Long waitTime = (one.getStartTime().getTime() - one.getArriveTime().getTime())
                / (RConst.Num.INT_1000*RConst.Num.LONG_60);
        orderBill.setWaitingMinute(waitTime.intValue());//单位分钟
        if(orderBill.getFreeBaseWaitingMinute()<waitTime){
            //计算超出分钟数
            int overWaitTime = waitTime.intValue()-orderBill.getFreeBaseWaitingMinute();
            orderBill.setExeceedBaseWaitingMinute(
                    new BigDecimal(overWaitTime));
            //计算超出分钟数价格
            BigDecimal multiply = orderBill.getExeceedBaseWaitingEveryKmAmount().multiply(new BigDecimal(overWaitTime));
            orderBill.setWaitingAmount(multiply);
        }
        //返回里程数
        orderBill.setReturnMileage(one.getRealMileage());
        if(orderBill.getReturnMileage().compareTo(orderBill.getFreeBaseReturnMileage())>RConst.Num.INT_0){
            //计算超出返回里程数
            orderBill.setExceedFreeBaseReturnMileage(orderBill.getReturnMileage()
                    .subtract(orderBill.getFreeBaseReturnMileage()));
            //计算返程超出费用
            orderBill.setReturnAmont(orderBill.getExceedFreeBaseReturnMileage()
                    .multiply(orderBill.getExceedBaseReturnEveryKmAmount()));
        }
        orderBill.setParkingAmount(new BigDecimal(confirmFeesDto.getParkingFee()));
        orderBill.setUpdateTime(new Date());
        orderBillMapper.updateById(orderBill);
        return orderBill;
    }

    private void updateOrder(Order one) {
        //实际里程  -- 从hbase里面去取
        //1.调用hbase根据orderNo去获取坐标点系
        //2.使用腾讯地图工具类 将坐标系传入 得到 坐标系距离
        //todo
        one.setRealMileage(one.getExpectsMileage());
        //返程里程
        one.setReturnMileage(one.getRealMileage());
        //实际订单价格
        JSONResult valuation = ruleStartRemoteApi.valuation(one.getExpectsMileage().toString());
        if(!valuation.isSuccess()){
            throw new GlobalException(ErrorCode.SERVICE_ERROR);
        }
        one.setRealOrderAmount(new BigDecimal(valuation.getData().toString()));
        //修改订单状态
        one.setStatus(Constants.orderStatus.ORDER_STATUS_ENSURE);
        one.setUpdateTime(new Date());
        super.updateById(one);
    }


    /**
     * order 是推给多个司机
     * 20个人同一时间  都发送请求到后台
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult  killOrderHandle(String orderNo) {
        //要判断一下自己司机有没有接单
        //方案1 在redis存放一个司机的状态  前端解决的
        //使用redssion的可重入锁
        RLock lock = redissonClient.getLock(Constants.ACCEPT_ORDERS_LOCK);
        try {
            lock.lock();
            //根据订单号 查询订单
            Order one = super.getOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderNo, orderNo)
                    .eq(Order::getStatus,Constants.orderStatus.ORDER_STATUS_WAIT));
            if(one != null){
                //司机接单
                acceptOrders(one);
                OrderInfoVo orderInfoVo = new OrderInfoVo();
                BeanUtils.copyProperties(one,orderInfoVo);
                orderInfoVo.setFavourFee(one.getFavourAmount());
                return JSONResult.success(orderInfoVo);
            }
            return JSONResult.error(ErrorCode.SERVICE_ERROR);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public JSONResult checkRunningOrder() {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.orderStatus.getCheckRunningOrder()));
        if(one == null){
            return JSONResult.success();
        }
        Position from = new Position();
        from.setAddress(one.getStartPlace());
        from.setLongitude(one.getStartPlaceLongitude());
        from.setLatitude(one.getStartPlaceLatitude());
        Position to = new Position();
        to.setAddress(one.getEndPlace());
        to.setLongitude(one.getEndPlaceLongitude());
        to.setLatitude(one.getEndPlaceLatiude());
        return JSONResult.success(new RunningOrderInfoVo(from,to));
    }

    @Override
    public JSONResult driverRunningOrder() {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.orderStatus.getCheckRunningOrder()));
        OrderInfoVo orderInfoVo = new OrderInfoVo();
        BeanUtils.copyProperties(one,orderInfoVo);
        orderInfoVo.setFavourFee(one.getFavourAmount());
        return JSONResult.success(orderInfoVo);
    }

    /**
     * 应该使用post请求
     * 请求参数是  当前司机的 位置 坐标
     * 在从redis里面取  起始点的位置
     * 在用geo去计算 两个点的坐标位置要小于 0.1千米  才能修改状态
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult arriveStartPlaceHandle(String orderNo) {
        Date date = new Date();
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        one.setStatus(Constants.orderStatus.ORDER_STATUS_ARRIVE);
        one.setUpdateTime(date);
        one.setArriveTime(date);
        super.updateById(one);
        return JSONResult.success();
    }

    @Override
    public JSONResult startDrivingHandle(String orderNo) {
        Date date = new Date();
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        one.setStatus(Constants.orderStatus.ORDER_STATUS_START_DRIVING);
        one.setUpdateTime(date);
        one.setStartTime(date);
        super.updateById(one);
        return JSONResult.success();
    }

    @Override
    public JSONResult endDrivingHandle(String orderNo) {
        Date date = new Date();
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        one.setStatus(Constants.orderStatus.ORDER_STATUS_COMPLETE_DRIVED);
        one.setUpdateTime(date);
        one.setEndTime(date);
        super.updateById(one);
        return JSONResult.success();
    }


    /**
     * 司机接单修改订单
     */
    private void acceptOrders(Order one) {
        // 如果是0 修改状态为1
        one.setStatus(Constants.orderStatus.ORDER_STATUS_ACCEPTED);//司机接单了
        //      修改订单状态 记录订单数据
        Date date = new Date();
        long loginIdAsLong = StpUtil.getLoginIdAsLong();//当前登录司机的id
        one.setDriverId(loginIdAsLong);
        one.setAcceptTime(date);
        //从stpUtil取出来的扩展参数是jsonObject
        Object extra = StpUtil.getExtra(Constants.CURRENT_LOGIN_KEY);
        //先转JSON字符串在转对象
        Login login = JSONObject.parseObject(extra.toString(), Login.class);
        one.setDriverName(login.getName());
        one.setDriverPhone(login.getPhone());
        one.setDriverPhoto(login.getAvatar());
        one.setUpdateTime(date);
        super.updateById(one);
    }

    @Override
    public JSONResult getNewOrderByMQ() {
        //要从MQ里面主动获取消息
        // 知道 TOPIC TAG driverId
        // 从MQ里面去取值  用的是pull

        List<PushDriverOrderMsgDto> result = new ArrayList<PushDriverOrderMsgDto>();
        long driverId = StpUtil.getLoginIdAsLong();//当前请求的司机id
//消息的路径
//RocketMQConstants.PUSH_DRIVER_ORDER_TOPIC+":"+String.format(RocketMQConstants.PUSH_DRIVER_ORDER_TAG,driverId);

        try {
            //得到对应的topic的队列
            Set<MessageQueue> messageQueues =
                    pullConsumer.fetchSubscribeMessageQueues(RocketMQConstants.PUSH_DRIVER_ORDER_TOPIC);
            //循环队列
            for (MessageQueue messageQueue : messageQueues) {
                //通过偏移量来获取消息 ：当前队列的消费位置
                long offset = pullConsumer.fetchConsumeOffset(messageQueue, true);
                String tag = String.format(RocketMQConstants.PUSH_DRIVER_ORDER_TAG, driverId);
                //取获取 那个Queue里面的那个tag的那个offset的值
                PullResult pullResult = pullConsumer.pull(messageQueue, tag, offset, 32);

                //找到消息
                if (pullResult != null && pullResult.getPullStatus().equals(PullStatus.FOUND)) {
                    //拿到消息列表
                    List<MessageExt> messageExtList = pullResult.getMsgFoundList();
                    if(messageExtList == null || messageExtList.size() == 0)continue;
                    for (MessageExt messageExt : messageExtList) {
                        //拿到消息内容，转换为订单对象
                        String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
                        PushDriverOrderMsgDto msgDto = JSON.parseObject(message, PushDriverOrderMsgDto.class);
                        //添加到结果列表
                        result.add(msgDto);
                    }
                }
                //修改offset的位置
                if(pullResult != null){
                    //重要：修改消息的消费位置，如果位置不后移，消息会一直被消费
                    pullConsumer.updateConsumeOffset(messageQueue,pullResult.getNextBeginOffset());
                }
            }
            return JSONResult.success(result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public JSONResult cancelCustomer(String orderNo) {
        //手动取消订单

        //只需要判断当前订单是否已经被司机接
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo,orderNo));
        if(one == null){
            return JSONResult.success();
        }

        if(one.getStatus().equals(RConst.Num.INT_0) ){//没有人接单
            one.setStatus(Constants.orderStatus.ORDER_STATUS_CUSTOMER_CANCEL_ORDER);
            one.setUpdateTime(new Date());
            super.updateById(one);
        }else if(one.getStatus().equals(Constants.orderStatus.ORDER_STATUS_ACCEPTED)) {
            //以下就是
            // 可以收取手续费
            // 可以扣减信誉度
            one.setStatus(Constants.orderStatus.ORDER_STATUS_CUSTOMER_CANCEL_ORDER);
            one.setUpdateTime(new Date());
            super.updateById(one);
        }
        return JSONResult.success();
    }


    /**
     * 1.保存订单数据--(todo保存账单)
     * 2.根据起点的经纬度找redis里面 一定范围司机 -- 多个
     * 3.从多个司机找对应的redis的配置  过滤不符合 司机
     * 4.将对应的订单推送给 符合标准的司机（MQ推送）
     * 5.返回 成功或者失败
     * @param orderDto
     * @return
     */
    @Override
    @Transactional
    public JSONResult save(OrderDto orderDto) {
        //要保证 同一个用户一次只能下一个单
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.orderStatus.getRunOrderStatus()));
        if(one!=null){
            return JSONResult.error("你有订单正在运行");
        }
        long loginId = StpUtil.getLoginIdAsLong();
        String no = CodeGenerateUtils.generateOrderSn(loginId);
        //保存订单
        Order order = saveOrder(orderDto, no, loginId);
        //初始化账单
        initOrderBill(order);
        //去匹配司机
        List<GeoDriverDto> drivers = getDrivers(orderDto);
        //根据司机的id找符合配置司机
        List<GeoDriverDto> driversSetting = getDriversSetting(drivers, order);
        //发送消息
        pushOrderMessage(driversSetting,order);//为什么要使用MQ
        return JSONResult.success(no);
    }

    /**
     * 给司机推送订单消息
     * @param drivers
     */
    private void pushOrderMessage(List<GeoDriverDto> drivers,Order order) {
        for (GeoDriverDto driver : drivers) {
            //构建发送消息的数据
            PushDriverOrderMsgDto pushDriverOrderMsgDto = new PushDriverOrderMsgDto();
            BeanUtils.copyProperties(order,pushDriverOrderMsgDto);
            pushDriverOrderMsgDto.setExpectsMileage(order.getExpectsMileage().toString());
            if(pushDriverOrderMsgDto.getFavourAmount() == null){
                pushDriverOrderMsgDto.setFavourAmount(new BigDecimal(RConst.Num.INT_0));
            }
            //做转换
            DecimalFormat df=new DecimalFormat("######.######");
            String result=df.format(driver.getValue());
            pushDriverOrderMsgDto.setToDriverMileage(result);//到司机的距离
            Message<PushDriverOrderMsgDto> message = withPayload(pushDriverOrderMsgDto).build();
            //发送到MQ里面
            rocketMQTemplate.syncSend(RocketMQConstants.PUSH_DRIVER_ORDER_TOPIC+":"
                    +String.format(RocketMQConstants.PUSH_DRIVER_ORDER_TAG,driver.getDriverId()),
                    message);
        }
    }

    /**
     * 根据司机的id找符合配置司机
     * @param drivers
     */
    private List<GeoDriverDto> getDriversSetting(List<GeoDriverDto> drivers,Order order) {
        List<GeoDriverDto> driverIds = new ArrayList<>();
        for (GeoDriverDto dto : drivers) {
            //判断司机是否还在线
            Boolean b = redisTemplate.hasKey(CacheConstants.DRIVER_POINT_SETTING + dto.getDriverId());
            if(!b){
                continue;
            }
            //查询司机的配置
            DriverSettingRedisDto setting = (DriverSettingRedisDto)redisTemplate.opsForValue()
                    .get(CacheConstants.DRIVER_POINT_SETTING + dto.getDriverId());
            //判断司机最大接受里程数
            Integer orderDistance = setting.getOrderDistance();
            if(order.getExpectsMileage().intValue() > orderDistance){
                continue;
            }
            //司机没有开启接单
            if(!setting.getListenService()){
                continue;
            }
            //判断当前订单是否在司机的接受范围内
            if(dto.getValue()>setting.getRangeDistance()){
                continue;
            }
            driverIds.add(dto);
        }
        return driverIds;
    }

    /**
     * 寻找司机
     *      递归  内存异常 或者是 栈溢出
     *
     * @param orderDto
     */
    public List<GeoDriverDto> getDrivers(OrderDto orderDto){
        //得到乘客开始位置的经纬度
        // 起始点 //得到司机
        Point point = new Point(Double.valueOf(orderDto.getStartPlaceLongitude()),
                Double.valueOf(orderDto.getStartPlaceLatitude()));
        //设置半径
        Circle circle = new Circle(point, new Distance(RConst.Num.BASE_LONG_2, Metrics.KILOMETERS));
        //构建查询参数
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        args.includeDistance()//结果中包含 距离
            .includeCoordinates()//结果中包含坐标
            .sortAscending()//由近到远
            .limit(RConst.Num.BASE_LONG_5);//匹配多少个
        //得到范围类的司机 多个
        GeoResults<RedisGeoCommands.GeoLocation<Object>> radius =
                redisTemplate.opsForGeo().radius(CacheConstants.DRIVER_POINT, circle, args);
        //得到list集合
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = radius.getContent();
        //取司机的id
        return content.stream()
                .map(item ->
                    new GeoDriverDto(Long.valueOf(item.getContent().getName().toString()),
                        item.getDistance().getValue(),
                        item.getDistance().getUnit())
                ).collect(Collectors.toList());
    }

    /**
     * 计算订单明细
     * @param
     */
    private void initOrderBill(Order order) {
        OrderBill orderBill = new OrderBill();
        orderBill.setOrderId(order.getId());
        orderBill.setOrderNo(order.getOrderNo());
        orderBill.setCreateTime(new Date());
        JSONResult result = ruleStartRemoteApi.getRuleDto(LocalDateTime.now().getHour());
        Object data = result.getData();
        RuleDto ruleDto = JSONObject.parseObject(JSON.toJSONString(data),RuleDto.class);
        BeanUtils.copyProperties(ruleDto,orderBill);
        orderBillMapper.insert(orderBill);
    }

    private Order saveOrder(OrderDto orderDto,String no,Long loginId) {
        //请求的数据已经搞定
        Order order = OrderDto.orderDtoToOrder(orderDto);
        order.setOrderNo(no);
        order.setCustomerId(loginId);
        order.setCreateTime(new Date());
        order.setStatus(Constants.orderStatus.ORDER_STATUS_WAIT);
        //从stpUtil取出来的扩展参数是jsonObject
        Object extra = StpUtil.getExtra(Constants.CURRENT_LOGIN_KEY);
        //先转JSON字符串在转对象
        Login login = JSONObject.parseObject(extra.toString(), Login.class);
        order.setCustomerName(login.getName());
        order.setCustomerPhone(login.getPhone());
        order.setCustomerPhoto(login.getAvatar());
        //计算预计费用
        JSONResult valuation = ruleStartRemoteApi.valuation(orderDto.getExpectsMileage().toString());
        if(!valuation.isSuccess()){
            throw new GlobalException(ErrorCode.SERVICE_ERROR);
        }
        order.setExpectsOrderAmount(new BigDecimal(valuation.getData().toString()));
        super.save(order);//mybatisPlus执行添加后会自动返回id到对象里面
        return order;
    }
}
